package com.atguigu.gulimall.product.service.impl;

import com.atguigu.common.constant.ProductConstant;
import com.atguigu.gulimall.product.dao.AttrAttrgroupRelationDao;
import com.atguigu.gulimall.product.dao.AttrGroupDao;
import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.AttrAttrgroupRelationEntity;
import com.atguigu.gulimall.product.entity.AttrGroupEntity;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import com.atguigu.gulimall.product.vo.AttrGroupRelationVO;
import com.atguigu.gulimall.product.vo.AttrRespVO;
import com.atguigu.gulimall.product.vo.AttrVO;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.product.dao.AttrDao;
import com.atguigu.gulimall.product.entity.AttrEntity;
import com.atguigu.gulimall.product.service.AttrService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;


@Service("attrService")
public class AttrServiceImpl extends ServiceImpl<AttrDao, AttrEntity> implements AttrService {
    @Resource
    private AttrAttrgroupRelationDao attrAttrgroupRelationDao;
    @Resource
    private AttrGroupDao attrGroupDao;
    @Resource
    private CategoryDao categoryDao;
    @Resource
    private CategoryService categoryService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<AttrEntity> page = this.page(
                new Query<AttrEntity>().getPage(params),
                new QueryWrapper<AttrEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 规格参数的列表查询,前端发来的关键参数有attrType,catelogId,params
     * attrType:
     * attr=0表示查询的是销售属性,不需要返回所属分组,(人家前端不展示,你返回了也没用,不信去前端看,销售属性就没有展示所属分组)
     * attr=1表示查询的是base基本属性,也叫规格参数,需要返回所属分组
     * <p>
     * catelogId:就是左边那个分类的树
     * 0表示我们还没有点击那个树,直接显示全部的attr
     * 当我们点击手机->手机通讯->手机 这个值就是225,展示的是分类为手机的属性
     * <p>
     * params:索引key
     * key="" 表示我们没有设置索引条件,因此查询全部
     * key="上市" 表示我们设置了索引条件,开始条件查询
     */
    @Override
    public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String type) {
        QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<AttrEntity>().eq("attr_type", "base".equalsIgnoreCase(type) ? ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() : ProductConstant.AttrEnum.ATTR_TYPE_SALE.getCode());
        // 分页查询基本方法
        if (catelogId != 0) {
            // !=0是根据条件查询(前端默认传catelogId=0),==0是直接查询所有的数据
            queryWrapper.eq("catelog_id", catelogId);
        }
        String key = (String) params.get("key");
        if (!StringUtils.isEmpty(key)) {// 如果检索条件存在
            queryWrapper.and((wrapper) -> { // 这个wrapper是形参,名字随便起
                wrapper.eq("attr_id", key).or().like("attr_name", key);
            });
        }
        IPage<AttrEntity> page = this.page(
                new Query<AttrEntity>().getPage(params),
                queryWrapper);
        PageUtils pageUtils = new PageUtils(page);
        /**
         * 本应到这里结束,return pageUtils;完事,但是
         * 这里面的数据是不够的还缺两个字段catelogName和groupName也得响应给前端,看开发文档
         * 我们本表(pms_attr)中就有catelogId,所以catelogName不在话下,本表中有attr_Id(自家表的主键),也能拿到groupId
         * 通过catelogId拿pms_category表的name这多简单,根据id拿实体,从实体中取出name(分类名称)soeasy
         *
         * attrId不仅是自家表的主键,还是中间表的外键,中间表有attr_id,attr_group_id两个外键
         * 1.通过attr_id获取中间表实体
         * 2.从中间表实体中拿到attr_group_id
         * 3.去pms_attr_group表中根据groupId去查groupEntity,从中就可以取到groupName了
         */


        List<AttrEntity> records = page.getRecords();// 从真正分页查询里面得到recodes
        System.out.println("records" + records);
        /**
         * 打印出来的是
         * records[AttrEntity(attrId=1, attrName=上市年份, searchType=0, icon=xxx1, valueSelect=2018;2019, attrType=1, enable=1, catelogId=225, showDesc=0),
         * AttrEntity(attrId=2, attrName=入网型号, searchType=1, icon=xxxx, valueSelect=aaa;bb, attrType=1, enable=1, catelogId=225, showDesc=1),
         * AttrEntity(attrId=6, attrName=电池容量, searchType=0, icon=xxx, valueSelect=5000mAh(typ), attrType=1, enable=1, catelogId=225, showDesc=0),
         * AttrEntity(attrId=7, attrName=电池可拆卸, searchType=0, icon=xxxx, valueSelect=可拆卸;不可拆卸, attrType=1, enable=1, catelogId=225, showDesc=0)]
         * 看来打印出来的是查询到的信息,即records是查询到的attrEntity的集合
         */
        List<AttrRespVO> respVOs = records.stream().map(attrEntity -> {
            AttrRespVO attrRespVO = new AttrRespVO();
            BeanUtils.copyProperties(attrEntity, attrRespVO);// attrRespVO就有了attrEntity里面的基本数据
            // 设置分组信息
            // 如果我们传过来的类型是base,才需要设置分组信息否则不用设置了；总结:如果是销售属性就不用设置分组信息了
            if ("base".equalsIgnoreCase(type)) {
                // 根据attrId去查中间表的详细信息,注意attrId不仅是attr表的主键，还是中间表的外键,所以我拿attrId去查中间表,不过分把?
                AttrAttrgroupRelationEntity attrId = attrAttrgroupRelationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
                if (attrId != null && attrId.getAttrGroupId() != null) {
                    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrId.getAttrGroupId());
                    attrRespVO.setGroupName(attrGroupEntity.getAttrGroupName());
                }
            }


            // 设置三级分类信息
            CategoryEntity categoryEntity = categoryDao.selectById(attrEntity.getCatelogId());
            if (categoryEntity != null) {
                attrRespVO.setCatelogName(categoryEntity.getName());
            }
            return attrRespVO;
        }).collect(Collectors.toList());
        pageUtils.setList(respVOs);
        return pageUtils;
    }

    /**
     * 查询规格(就是属性表)他三级分类的完整路径
     * select 语句必须判断是否为null
     * 有了attrId,我们就能拿到attrEntity实体,从中取出catelog_id,就可以去pms_category中查到分类实体,拿到分类名,然后显示他的所属分类
     * 有了attrId,我们就能拿到中间表实体实体,从中取出attr_group_id,就可以去pms_attr_group中查到分组实体,拿到分组名,然后显示他的所属分组
     */
    @Cacheable(value = "attr",key = "'attrinfo'+#root.args[0]")// args[0]就是第一个参数就是attrId
    @Override
    public AttrRespVO getAttrInfo(Long attrId) {
        AttrRespVO respVO = new AttrRespVO();
        // 先查当前属性的详细信息
        AttrEntity attrEntity = this.getById(attrId);// PO 和数据库中记录是一一对应的
        BeanUtils.copyProperties(attrEntity, respVO);// 先封一部分(数据库中的基本属性)的进VO

        // 一.设置分组信息,规格或叫基本信息才需要分组,销售属性不需要设置分组
        // 查询前端传来的attrId他的中间表信息attrgroupRelation
        if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {
            // 拿到中间表实体实体
            AttrAttrgroupRelationEntity attrgroupRelation = attrAttrgroupRelationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));

            /**
             * 可以考虑这么写
             * if (attrgroupRelation == null) throw new RuntimeException("attrId不合法");
             * respVO.setAttrGroupId(attrgroupRelation.getAttrGroupId());
             * AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupRelation.getAttrGroupId());
             * if(attrGroupEntity != null) throw new RuntimeException("没有找到该属性的属性分组信息");
             * respVO.setGroupName(attrGroupEntity.getAttrGroupName());
             * 比if嵌套好看多了,只是我们暂时还没有进行异常处理优化
             */

            if (attrgroupRelation != null) {
                // 取出attr_group_id
                respVO.setAttrGroupId(attrgroupRelation.getAttrGroupId());
                // 根据取出的attr_group_id,去pms_attr_group中查到分组实体
                AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupRelation.getAttrGroupId());
                if (attrGroupEntity != null) {
                    // 拿到分组名
                    respVO.setGroupName(attrGroupEntity.getAttrGroupName());// 分组名也有了
                }
            }
        }

        // 二.设置分类信息
        Long catelogId = attrEntity.getCatelogId();// 从中取出catelog_id
        Long[] catelogPath = categoryService.findCatelogPath(catelogId);// 查询完整路径这个方法我们以前写过哦,直接用
        respVO.setCatelogPath(catelogPath);// 前端还要当前属性的完整分类路径
        // 来都来了,顺便把分类的名字也给响应回去把
        CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
        if (categoryEntity != null) {
            respVO.setCatelogName(categoryEntity.getName());
        }

        return respVO;
    }

    /**
     * 获取属性分组关联的所有属性
     * 1.我们只用通过attrgroupId获取中间表的实体类
     * 2.从中间表的实体类中取出attr_id
     * 3.根据attr_id再去pms_attr表里查到对应的attrEntity,响应给前端就over
     */
    @Override
    public List<AttrEntity> getRelationAttr(Long attrgroupId) {
        // 根据属性分组id去中间表中查详细信息,从而拿到属性的id
        List<AttrAttrgroupRelationEntity> entities = attrAttrgroupRelationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id", attrgroupId));
        List<Long> attrIds = entities.stream().map(AttrAttrgroupRelationEntity::getAttrId).collect(Collectors.toList());
        if (attrIds == null || attrIds.size() == 0) {
            return null;
        }
        List<AttrEntity> attrEntities = this.listByIds(attrIds);// 就是attrService.listByIds(attrIds);
        return attrEntities;
    }

    /**
     * 获取当前分组分组没有关联的所有属性
     */
    @Override
    public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrgroupId) {
        // 要点1.当前属性分组只能关联自己所属分类里面的属性,肯定关联不到别的类中
        /**
         *
         * 思路:先找到属性分组所在的本类:在pms_attr_group分组表里根据attr_group_id找到分组实体，从中取出catelog_id
         */
        AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupId);//当前分组信息
        Long catelogId = attrGroupEntity.getCatelogId();
        // 要点2.当前属性分组只能关联别的属性分组没有引用的属性
        /**
         * 思路:
         * 1.找到当前分类下的所有分组
         *   在pms_attr属性表里根据catelog_id找到属性实体;
         *   select * from pms_attr_group where catelog_id = #{catelog_id}
         *   就能拿到当前分类下的所有分组
         * 2.找到这些分组关联的属性
         *   2.1.从上一步查到的分组实体中取出他们的id,即attr_group_id;
         *   2.2.根据attr_group_id去中间表中获取中间表的实体,从中取出attr_id
         * 至此就能找到分组关联的属性他们的id
         * 3.从当前分类的所有属性中剔除这些属性,注意啊,你首先得保证这些属性都在当前分类下的,然后还不能是销售属性,最后再剔除(not in)
         *   (他无意间也帮我们剔除了自己已经关联了的属性了,太棒了)
         */
        // ne 就是not equal,当前这个attr_group_id不能是我现在正在用的attrgroupId
        /*思路1*/
        List<AttrGroupEntity> group = attrGroupDao.selectList(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));
        /*思路2.1*/
        List<Long> attrGroupIds = group.stream().map(AttrGroupEntity::getAttrGroupId).collect(Collectors.toList());
        /*思路2.2*/
        List<AttrAttrgroupRelationEntity> middleEntity = attrAttrgroupRelationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().in("attr_group_id", attrGroupIds));// 就是where attr_group_id in #{attrGroupIds}
        List<Long> attrIds = middleEntity.stream().map(AttrAttrgroupRelationEntity::getAttrId).collect(Collectors.toList());

        // 首先我们肯定要保证这些属性是在当前分类下啊,你不能让我关联别的分类下的属性(规格参数)啊,然后再剔除notIn这些属性
        /*思路3*/
        QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>().eq("catelog_id", catelogId).eq("attr_type", ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());
        if (attrIds != null && attrIds.size() > 0) { // attrIds不为空时,我们才能从中剔除attrIds
            wrapper.notIn("attr_id", attrIds);
        }

        // 任务至此完成,下面是对查询条件的处理
        String key = (String) params.get("key");// 如国有模糊查询条件的话
        if (!StringUtils.isEmpty(key)) {
            wrapper.and((w) -> {
                w.eq("attr_id", key).or().like("attr_name", key);
            });
        }
        IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), wrapper);//params包含页面传来的分页参数和索引条件
        PageUtils pageUtils = new PageUtils(page);
        return pageUtils;
    }
    // 在指定的所有属性集合里面,挑出可以被检索的属性
    @Override
    public List<Long> selectSearchAttrs(List<Long> attrIds) {
        return baseMapper.selectSearchAttrsIds(attrIds);
    }


    // 新增规格,也叫属性Attr
    @Transactional
    @Override
    public void saveAttr(AttrVO attr) {// attr是页面传输过来的数据AttrVO
        // 1.先保存基本信息
        AttrEntity attrEntity = new AttrEntity();// 这是个PO,用来对应数据库的
        /*attrEntity.setAttrName(attr.getAttrName());
        attrEntity.getAttrType(attr.getAttrType());*/
        /**
         * 一个一个的封装太累了(见上面注释那两行代码),怎么办呢?我们有BeanUtils
         * org.springframework.beans.BeanUtils 包别导错
         * copyProperties()的第一个参数:Object source:数据的来源 ,第二个参数是复制到哪个对象里
         * 就是把页面来的数据attr(VO)封装到attrEntity(PO)中,VO中有的数据是不需要存到数据库的,比如说attrGroupId,pms_attr表里没有这个字段
         *
         */
        BeanUtils.copyProperties(attr, attrEntity);// attr是页面传输过来的数据AttrVO
        this.save(attrEntity);// 相当于attrServiceImpl.save(attrEntity);新增到pms_attr表里,注意,已经存入到表里面了,id会自增,所以才是attrEntity的attrId就不再是AttrVO给的null了,而是自增的id
        // 2.再保存关联的信息
        // attr.getAttrType() == 1 说明是基本属性(就是规格),才需要设置分组关系，否则是销售属性,不需要设置分组关系,你去看下页面就懂了,规格参数有所属分组,销售属性没有所属分组
        if (attr.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() && attr.getAttrGroupId() != null) {
            AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();// 中间表PO
            relationEntity.setAttrGroupId(attr.getAttrGroupId());// 传来的attr里面有attrGroupId,给中间表赋值就可以了
            relationEntity.setAttrId(attrEntity.getAttrId());// 注意哦,传来的attr的attrId=null,因为新增不需要传id,所以我们不能从attr里面获取id,只要把attrEntity存到数据表里,id就会自增,他就有id了
            attrAttrgroupRelationDao.insert(relationEntity);// 我才发现mybatis-plus中dao接口的新增方法名和service中新增的方法名字不一样
            // 至此,中间表里有attrId,也有attrGroupId,中间表新增完成
        }

    }

    // 修改,也叫属性Attr
    @Transactional
    @Override
    public void updateAttr(AttrVO attr) {
        // 修改基本数据,这些数据都来源于页面
        AttrEntity attrEntity = new AttrEntity();
        BeanUtils.copyProperties(attr, attrEntity);
        this.updateById(attrEntity);

        if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {
            /**
             * 修改分组关联思路:
             * 1.根据attr规格(属性)找到当前psm_attr的中间表
             * 2.从中间表pms_attr_group_relation拿到他对应的属性分组id
             * 3.通过属性分组id再去属性分组pms_attr_group中找到具体具体的分组属性
             */
            AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();// 中间表的实体
            // 注意给这个attr是AttrVO不是AttrEntity,里面存储的有基本属性,attrGroupId,groupName,catelogName,catelogPath太方便了
            // 这个简单,GroupId和AttrId这两个字段里面AttrVO都有
            relationEntity.setAttrGroupId(attr.getAttrGroupId());
            relationEntity.setAttrId(attr.getAttrId());

            // 从关系表中查到等于属性id的总数
            Long count = attrAttrgroupRelationDao.selectCount(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
            if (count > 0) {
                attrAttrgroupRelationDao.update(relationEntity, new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
            } else {
                attrAttrgroupRelationDao.insert(relationEntity);
            }
        }
    }

    // 将属性分组关联的属性信息给移除掉,只删除中间表数据,就是剔除关联
    @Override
    public void deleteRelation(AttrGroupRelationVO[] vos) {
        // List<AttrGroupRelationVO> attrGroupRelationVOS = Arrays.asList(vos); 类型不一致所以是map而不是peek
        List<AttrAttrgroupRelationEntity> entities = Arrays.asList(vos).stream().map((item) -> {
            AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();// 中间表
            BeanUtils.copyProperties(item, relationEntity);// item有两个属性,分别是 attrId=xx和attrGroupId=xx,这是前端传来的
            return relationEntity;
        }).collect(Collectors.toList());
        /**
         * vos:[AttrGroupRelationVO(attrId=7, attrGroupId=8)]  entities[AttrAttrgroupRelationEntity(id=null, attrId=7, attrGroupId=8, attrSort=null)]
         * 感觉上面那个方法就是把类型由AttrGroupRelationVO转成AttrAttrgroupRelationEntity,值还是那个值,没有变
         */
        System.out.println("entities" + entities);
        // 有了中间表的实体不就爽了,只要把中间表的数据删除,就完成关联剔除的功能了
        attrAttrgroupRelationDao.deleteBatchRelation(entities);
        // 注意,只是在中间表里剔除了,并没有删除属性表里的数据,所以属性表里数据依然有,只是所属分组是空的,因为中间表被删了,找不到了
    }


}